package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.common.core.constant.CacheConstants;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.bean.BeanUtils;
import com.spzx.product.domain.*;
import com.spzx.product.domain.vo.SkuStockVo;
import com.spzx.product.mapper.ProductDetailsMapper;
import com.spzx.product.mapper.ProductMapper;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.mapper.SkuStockMapper;
import com.spzx.product.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import jakarta.annotation.Resource;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author xiongkunYang
 */
@Slf4j
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {

    @Resource
    private ProductSkuMapper productSkuMapper;
    @Resource
    private SkuStockMapper skuStockMapper;
    @Resource
    private ProductDetailsMapper productDetailsMapper;
    @Resource
    private RedisTemplate<Object, Object> redisTemplate;
    @Resource
    private RedissonClient redissonClient;

    //查询商品列表
    @Override
    public List<Product> selectProductList(Product product) {
        return baseMapper.selectProductList(product);
    }

    //添加商品
    @Transactional
    @Override
    public int insertProduct(Product product) {
        //0 操作多张表，添加到事务里面
        //1 添加商品基本信息到product表
        baseMapper.insert(product);

        //2 获取前端传递过来商品所有sku列表list集合 productSkuList
        List<ProductSku> productSkuList = product.getProductSkuList();

        //3 把sku列表list集合遍历，得到每个sku对象，把每个sku添加product_sku
        for (int i = 0; i < productSkuList.size(); i++) {
            ProductSku productSku = productSkuList.get(i);
            //设置一些sku其他值
            productSku.setSkuCode(product.getId() + "_" + i);
            productSku.setProductId(product.getId());
            String skuName = product.getName() + " " + productSku.getSkuSpec();
            productSku.setSkuName(skuName);
            //把每个sku添加product_sku表
            productSkuMapper.insert(productSku);

            //4 把每个sku和库存量，添加sku_stock
            SkuStock skuStock = new SkuStock();
            skuStock.setSkuId(productSku.getId());
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setLockNum(0);
            skuStock.setAvailableNum(productSku.getStockNum());
            skuStock.setSaleNum(0);
            skuStockMapper.insert(skuStock);
        }

        //5 添加商品product_details
        // 商品图片 image_urls
        // detailsImageUrlList集合 转换字符串
        ProductDetails productDetails = new ProductDetails();

        List<String> detailsImageUrlList = product.getDetailsImageUrlList();
        // [01.jpg,02.jpg]  ==  01.jpg,02.jpg
        productDetails.setProductId(product.getId());

        String detailsImageUrl = String.join(",", detailsImageUrlList);
        productDetails.setImageUrls(detailsImageUrl);

        productDetailsMapper.insert(productDetails);

        return product.getId().intValue();
    }

    //获取商品详细信息
    @Override
    public Product selectProductById(Long id) {
        //1 根据商品id获取商品基本信息，查询product表
        Product product = baseMapper.selectById(id);

        //2 根据商品id获取商品所有sku列表
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductSku::getProductId, id);
        List<ProductSku> productSkuList = productSkuMapper.selectList(wrapper);

        //2.1 因为每个sku有对应库存量
        //2.2 查询每个sku库存量封装到每个ProductSku对象里面
        //第一种 productSkuList遍历，得到每个sku，根据每个skuid查询库存表得到库存量，设置对象
//        productSkuList.forEach(productSku -> {
//            Long skuId = productSku.getId();
//            //根据skuId查询库存
//            LambdaQueryWrapper<SkuStock> wrapper1 = new LambdaQueryWrapper<>();
//            wrapper1.eq(SkuStock::getSkuId,skuId);
//            SkuStock skuStock = skuStockMapper.selectOne(wrapper1);
//            Integer totalNum = skuStock.getTotalNum();
//
//            productSku.setStockNum(totalNum);
//        });

        //第二种 stream流数据处理
        //1 从productSkuList获取所有id
        //List<ProductSku>
        List<Long> skuIdList =
                productSkuList.stream().map(ProductSku::getId).collect(Collectors.toList());

        //2 拿着所有skuId集合查询库存表，对应库存信息
        LambdaQueryWrapper<SkuStock> wrapperSkuStock = new LambdaQueryWrapper<>();
        wrapperSkuStock.in(SkuStock::getSkuId, skuIdList);
        List<SkuStock> skuStockList = skuStockMapper.selectList(wrapperSkuStock);

        //3 第二步查询sku所有库存信息list ，转换map集合
        // key:skuId  value:库存量
        Map<Long, Integer> map =
                skuStockList.stream()
                        .collect(Collectors.toMap(SkuStock::getSkuId, SkuStock::getTotalNum));

        //4 把productSkuList遍历，根据skuId到第三步map里面找到对应库存，设置到productSku
        productSkuList.forEach(productSku -> {
            //获取skuId
            Long productSkuId = productSku.getId();
            //根据skuid查询map，获取对应库存量
            Integer num = map.get(productSkuId);
            productSku.setStockNum(num);
        });

        //3 把查询商品所有sku列表封装product里面
        product.setProductSkuList(productSkuList);

        //4 根据商品id获取商品详情数据  图片列表
        LambdaQueryWrapper<ProductDetails> wrapperDetails = new LambdaQueryWrapper<>();
        wrapperDetails.eq(ProductDetails::getProductId, id);
        ProductDetails productDetails = productDetailsMapper.selectOne(wrapperDetails);

        String imageUrls = productDetails.getImageUrls();
        // 01.jpg,02.jpg
        //字符串 imageUrls 转换数组
        String[] split = imageUrls.split(",");
        //把数组转换list集合
        List<String> list = Arrays.asList(split);
        //5 把查询商品图片列表封装product里面
        product.setDetailsImageUrlList(list);

        //6 返回封装完成product对象
        return product;
    }

    //修改
    @Transactional
    @Override
    public int updateProduct(Product product) {

        //1 删除缓存
        List<Long> skuIdList = product.getProductSkuList().stream()
                .map(ProductSku::getId).toList();
        skuIdList.forEach(skuId -> {
            String dataKey = "product:sku:" + skuId;
            this.redisTemplate.delete(dataKey);
        });

        //修改商品信息
        baseMapper.updateById(product);

        List<ProductSku> productSkuList = product.getProductSkuList();

        productSkuList.forEach(productSku -> {
            //修改商品SKU信息
            productSkuMapper.updateById(productSku);

            //修改商品库存
            SkuStock skuStock =
                    skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>()
                            .eq(SkuStock::getSkuId, productSku.getId()));
            skuStock.setTotalNum(productSku.getStockNum());

            int availableNum = skuStock.getTotalNum() - skuStock.getLockNum();

            skuStock.setAvailableNum(availableNum);
            skuStockMapper.updateById(skuStock);
        });

        //修改商品详细信息
        ProductDetails productDetails =
                productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, product.getId()));
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetailsMapper.updateById(productDetails);

        //3 休眠一段时间
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        //4 再次执行操作删除缓存
        skuIdList.forEach(skuId -> {
            String dataKey = "product:sku:" + skuId;
            this.redisTemplate.delete(dataKey);
        });
        return 1;
    }

    //删除
    @Transactional
    @Override
    public int deleteProductByIds(Long[] ids) {
        //1 根据商品id删除product
        baseMapper.deleteBatchIds(Arrays.asList(ids));

        //2 删除skuId，删除sku里面库存信息
        //获取商品所有skuId
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(ProductSku::getProductId, ids);
        List<ProductSku> productSkuList = productSkuMapper.selectList(wrapper);

        //productSkuList  获取skuId值
        List<Long> skuIdList =
                productSkuList.stream().map(ProductSku::getId).collect(Collectors.toList());

        //删除sku对应库存 删除条件 skuId
        LambdaQueryWrapper<SkuStock> wrapperSkuStock = new LambdaQueryWrapper<>();
        wrapperSkuStock.in(SkuStock::getSkuId, skuIdList);
        skuStockMapper.delete(wrapperSkuStock);

        //3 根据商品id删除product_sku
        productSkuMapper
                .delete(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getProductId, ids));

        //4 根据商品id删除详情
        productDetailsMapper
                .delete(new LambdaQueryWrapper<ProductDetails>()
                        .in(ProductDetails::getProductId, ids));
        return 1;
    }

    //查询畅销商品
    @Override
    public List<ProductSku> getTopSale() {
        return productSkuMapper.selectTopSale();
    }

    //远程调用使用，商品列表
    @Override
    public List<ProductSku> selectProductSkuList(SkuQuery skuQuery) {
        return productSkuMapper.selectProductSkuList(skuQuery);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int updateStatus(Long id, Integer status) {


        //商品状态
        Product product = new Product();
        product.setId(id);
        product.setStatus(status);

        if (status == 1) {
            //创建布隆过滤器
            RBloomFilter<Object> bloomFilter =
                    redissonClient.getBloomFilter("sku:bloom:filter");
            String key = "sku:product:data";
            List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>()
                    .eq(ProductSku::getProductId, id));
            productSkuList.forEach(item -> {
                Long itemId = item.getId();
                bloomFilter.add(itemId);
                redisTemplate.opsForValue().setBit(key, itemId, true);
            });
        }

        int updated = baseMapper.updateById(product);

        //商品sku状态
        LambdaUpdateWrapper<ProductSku> updateWrapperSku = new LambdaUpdateWrapper<ProductSku>()
                .eq(ProductSku::getProductId, id)
                .set(ProductSku::getStatus, status);
        int skuUpdated = productSkuMapper.update(null, updateWrapperSku);

        return updated + skuUpdated;
    }

    //    @GuiguCache(prefix = "product:")
    @Override
    public ProductSku getProductSku(Long skuId) {
        Boolean isExistBit = redisTemplate.opsForValue().getBit("sku:product:data", skuId);
        if (Boolean.FALSE.equals(isExistBit)) {
            log.error("用户查询商品sku不存在：{}", skuId);
            throw new ServiceException("用户查询商品sku不存在");
        }
        try {
            //1.优先从缓存中获取数据
            //1.1 构建业务数据Key 形式：前缀+业务唯一标识
            String dataKey = "product:sku:" + skuId;
            //1.2 查询Redis获取业务数据
            ProductSku productSku = (ProductSku) redisTemplate.opsForValue().get(dataKey);
            //1.3 命中缓存则直接返回
            if (productSku != null) {
                log.info("命中缓存，直接返回，线程ID：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                return productSku;
            }

            //2.尝试获取分布式锁（set k v ex nx可能获取锁失败）
            //2.1 构建锁key
            String lockKey = CacheConstants.LOCK_KEY_PRODUCT_SKU + skuId;
            //2.2 采用UUID作为线程标识
            String lockValue = UUID.randomUUID().toString().replaceAll("-", "");
            //2.3 利用Redis提供set nx ex 获取分布式锁
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(flag)) {
                //3.获取锁成功执行业务,将查询业务数据放入缓存Redis
                log.info("获取锁成功：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                try {
                    //再次查询缓存
                    productSku = (ProductSku) redisTemplate.opsForValue().get(dataKey);
                    //如果存在则直接返回
                    if (productSku != null) {
                        log.info("命中缓存，直接返回，线程ID：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                        return productSku;
                    }
                    //如果不存在则从数据库中查询
                    System.out.println("查询数据库");
                    productSku = productSkuMapper.selectById(skuId);
                    long ttl = 10;
                    // 稻草人模式，解决缓存穿透
                    if (productSku == null) {
                        //防止再次查询出来之后的值是null
                        productSku = new ProductSku();
                        ttl = 1;
                    }
                    //查询后将数据放入缓存
                    redisTemplate.opsForValue().set(dataKey, productSku, ttl, TimeUnit.MINUTES);
                    return productSku;

                } finally {
                    //4.业务执行完毕释放锁
                    String script = """
                            if redis.call('get', KEYS[1]) == ARGV[1] then
                                return redis.call('del', KEYS[1])
                            else
                                return 0
                            end
                            """;
                    redisTemplate.execute(
                            new DefaultRedisScript<>(script, Boolean.class),
                            List.of(lockKey),
                            lockValue
                    );
                }
            } else {
                try {
                    //5.获取锁失败则自旋（业务要求必须执行）
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.error("获取锁失败，自旋：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                return this.getProductSku(skuId);
            }
        } catch (RuntimeException e) {
            //兜底处理方案：Redis服务有问题，将业务数据获取自动从数据库获取
            log.error("[商品服务]查询商品信息异常：{}", e.getMessage(), e);
            return productSkuMapper.selectById(skuId);
        }
    }

    @Override
    public Product getProduct(Long id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public SkuPrice getSkuPrice(Long skuId) {
        SkuPrice skuPrice = new SkuPrice();
        ProductSku productSku = this.productSkuMapper.selectOne(new LambdaQueryWrapper<ProductSku>()
                .eq(ProductSku::getId, skuId));
        skuPrice.setMarketPrice(productSku.getMarketPrice());
        skuPrice.setSalePrice(productSku.getSalePrice());
        skuPrice.setSkuId(skuId);
        return skuPrice;
    }

    @Override
    public ProductDetails getProductDetails(Long productId) {
        return this.productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>()
                .eq(ProductDetails::getProductId, productId));
    }

    @Override
    public Map<String, Long> getSkuSpecValue(Long productId) {
        List<ProductSku> productSkus = this.productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>()
                .eq(ProductSku::getProductId, productId));
        return productSkus.stream().collect(Collectors.toMap(ProductSku::getSkuSpec, ProductSku::getId));
    }

    @Override
    public SkuStockVo getSkuStock(Long skuId) {
        SkuStock skuStock = this.skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>()
                .eq(SkuStock::getSkuId, skuId));
        SkuStockVo skuStockVo = new SkuStockVo();
        BeanUtils.copyProperties(skuStock, skuStockVo);
        return skuStockVo;
    }

    @Override
    public List<SkuPrice> getSkuPriceList(List<Long> skuIdList) {
        List<ProductSku> productSkus = productSkuMapper.selectBatchIds(skuIdList);
        return productSkus.stream().map(item -> {
            SkuPrice skuPrice = new SkuPrice();
            skuPrice.setMarketPrice(item.getMarketPrice());
            skuPrice.setSalePrice(item.getSalePrice());
            skuPrice.setSkuId(item.getId());
            return skuPrice;
        }).toList();
    }


}
